/*
Copyright (C) 2011 Salil Bhagurkar

This file is part of illusion

illusion is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

illusion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <kernel/console.h>
#include <kernel/kmalloc.h>
#include <klib/lib.h>
#include <klib/memory.h>
#include <klib/format.h>
#include <klib/string.h>
#include <arch/power.h>
#include <arch/portio.h>
#include <kernel/list.h>
#include <kernel/errors.h>
#include <kernel/init.h>
#include <kernel/vfs.h>
#include <kernel/sys.h>

//TODO Optimize this crap!

void debug_break_function()
{
}

/*
 * By default, the system will send all output of console_write to the
 * registered consoles, only if they are 'active'.
 */

static struct console_driver *console_drivers = null,
	*console_drivers_tail = null;

void register_console_driver(struct console_driver *driver)
{
	list_attach(console_drivers, console_drivers_tail, driver);
}

void unregister_console_driver(struct console_driver *driver)
{
	list_detach(console_drivers, console_drivers_tail, driver);
}

static void scroll_buffer_up(struct console_driver *driver)
{
	uint_t i;
	for(i = 0; i < (driver->height - 1); i++) {
		memcpy(driver->buffer + driver->width * i,
				driver->buffer +
				driver->width * (i + 1),
				driver->width);
	}
	memset(driver->buffer + (driver->height - 1) *
			driver->width, 0, driver->width);
	driver->cursor_y--;
}

static void scroll_buffer_down(struct console_driver *driver)
{
	uint_t i;
	for(i = (driver->height - 1); i >= 0; i++) {
		memcpy(driver->buffer + driver->width * i,
				driver->buffer +
				driver->width * (i - 1),
				driver->width);
	}
	driver->cursor_y--;
}


static void __console_writechar(struct console_driver *driver, char c)
{
	if(driver->flags & CONSOLE_BUFFERED) {
		uint_t ptr = driver->cursor_y * driver->width +
				driver->cursor_x;
		if(c == '\n') {
			driver->cursor_y++;
			driver->cursor_x = 0;
			if(driver->cursor_y >= driver->height)
				scroll_buffer_up(driver);
		} else {
			norm:
			driver->buffer[ptr] = c;
			driver->cursor_x++;
			if(driver->cursor_x >= driver->width) {
				driver->cursor_x = 0;
				driver->cursor_y++;
				if(driver->cursor_y >= driver->height)
					scroll_buffer_up(driver);
			}
		}
	} else {
		driver->write(&c, 1);
	}
}

void console_writechar(char c)
{
	struct console_driver *driver;
	list_for(console_drivers, driver) {
		if(driver->active) {
			__console_writechar(driver, c);
			if(driver->flags & CONSOLE_BUFFERED) {
				driver->update_hardware_cursor();
				driver->update_buffer(0, 0, driver->width,
						driver->height);
			}
		}
	}
}

static void __console_write(struct console_driver *driver, char *stream, uint_t len)
{
	uint_t i;
	for(i = 0; i < len; i++) {
		__console_writechar(driver, stream[i]);
	}
}

void console_write(char *stream, uint_t len)
{
	struct console_driver *driver;
	list_for(console_drivers, driver) {
		if(driver->active) {
			__console_write(driver, stream, len);
			if(driver->flags & CONSOLE_BUFFERED) {
				driver->update_hardware_cursor();
				driver->update_buffer(0, 0, driver->width,
						driver->height);
			}
		}
	}
	//delay(10);
}



void console_printf(char *fmt, ...)
{
	va_list list;
	va_start(list, fmt);
	char buffer[1024];
	uint_t length = format_string_va(buffer, 1024, fmt, list);
	console_write(buffer, length);
	va_end(list);
}


/*TODO this
static char *console_debug_allow = {CONFIG_DEBUG_ALLOW};
static char *console_debug_block = {CONFIG_DEBUG_BLOCK};
*/

static bool debug_output = false;

/*
 * console_printf like function which prints file, function and line no
 */
void console_debug(const char *file, const char *function, uint_t lineno,
		char *fmt, ...)
{
	if(!debug_output)
		return;
	va_list list;
	va_start(list, fmt);
//	if(strstartswith(file, "x86"))
//		return;
//	if(!streq(file, "kernel/vmpage.c"))
//		return;
	char hbuf[612], _buffer[512];
	char *buffer = _buffer;
	uint_t length = format_string_va(buffer, 512, fmt, list);

	bool noLoc = false;
	if(buffer[0] == '$')
	{
		if(buffer[1] == 'B') {
			noLoc = true;
			buffer += 2;
		}
	}
	uint_t hbufLen;
	if(noLoc) {
		hbufLen = format_string(hbuf, 612, buffer);
	} else {
		hbufLen = format_string(hbuf, 612, "[%u] %s(%u) %s(): %s", init_state,
			file, lineno, function, buffer);
	}
	console_write(hbuf, hbufLen);
	va_end(list);
}

void console_initmsg(const char *file, const char *function, uint_t lineno,
		char *fmt, ...)
{
	va_list list;
	va_start(list, fmt);
	char hbuf[612], buffer[512];
	uint_t length = format_string_va(buffer, 512, fmt, list);
	uint_t hbufLen;
	hbufLen = format_string(hbuf, 612, "[i%u] : %s", init_state, buffer);
	console_write(hbuf, hbufLen);
	va_end(list);
}

/*
 * Error in the system. This should halt the system
 */
void console_fatal_error(const char *file, const char *function, uint_t lineno,
		char *fmt, ...)
{
	va_list list;
	va_start(list, fmt);
	char hbuf[612], buffer[512];
	uint_t length = format_string_va(buffer, 512, fmt, list);
	uint_t hbufLen = format_string(hbuf, 612, "[%u] %s(%u) %s(): %s", init_state,
			file, lineno, function, buffer);
	console_write(hbuf, hbufLen);
	console_write("System Halted\n", 14);
	va_end(list);
	halt_system();
}

void console_hex(void *start, uint_t len)
{
	unsigned char *c;
	uint_t i, j, max;
	c = start;
	for(i = 0; i < len; i += 32) {
		console_printf("%x  ", c + i);
		max = 32 < (len - i) ? 32 : len - i;
		for(j = 0; j < max; j++) {
			unsigned char ch = *(unsigned char *)(c + i + j);
			if(ch >= '!' && ch <= '~') {
				console_printf("%c", ch);
			} else {
				console_printf(".");
			}
		}
		console_printf("\n");
	}
}

#define NR_CHARS2 16

void console_hex2(void *start, uint_t len)
{
	unsigned char *c;
	uint_t i, j, max;
	c = start;
	for(i = 0; i < len; i += NR_CHARS2) {
		console_printf("%x ", c + i);
		max = NR_CHARS2 < (len - i) ? NR_CHARS2 : len - i;
		for(j = 0; j < max; j++) {
			unsigned char ch = *(unsigned char *)(c + i + j);
			unsigned char d0 = ch & 0xf;
			console_writechar(' ');
			unsigned char d1 = (ch & 0xf0) >> 4;
			if(d1 > 0x9)
				console_writechar(d1 - 0xa + 'a');
			else
				console_writechar(d1 + '0');
			if(d0 > 0x9)
				console_writechar(d0 - 0xa + 'a');
			else
				console_writechar(d0 + '0');
		}
		console_printf("\n");
	}
}

static int console_sync_page(struct vfs_node *this, struct vfs_page *page)
{
	//Check for read/write
	if(page->flags & VFS_SYNC_MEM) {
		console_write(page->page_data, page->length);
	} else if(page->flags & VFS_SYNC_DEV) {
//		uint length = page->length;
//		while(length--) {
//			uint key;
//			char state;
//			read_input(&key, &state);
//		}
	}
	return 0;
}

static struct vfs_sync_provider console_prov = {
		.flags = VFS_SP_NOPAGECACHE,
		.name = "console_prov",
		.sync_node = null,
		.sync_page = console_sync_page,
		.get_child = null,
		.next_child = null,
};

int console_vfs_init()
{
	struct vfs_device *con_dev = vfs_create_device("console", 1);
	vfs_set_sync_provider(con_dev, &console_prov);
	return 0;
}

static struct vfs_node *console_sys, *console_debug_output;

static int default_write(void *buf, uint_t len)
{
	return ENOSUP;
}

static void *default_read(uint_t *len)
{
	*len = 0;
	return null;
}

static void *debug_output_sys_read(uint_t *len)
{
	char *buf = (char *)kmalloc(1, "dbgsys");
	*len = format_string(buf, 1, "%u", debug_output);
	return buf;
}

static int debug_output_sys_write(void *buf, uint_t len)
{
	debug_output = dec2string(buf);
	return 0;
}

int console_sys_init()
{
	initmsg("Creating console interfaces..\n");
	console_sys = vfs_create(sys_root, "console");
	console_debug_output = vfs_create(console_sys, "debug_output");
	register_sys_interface(console_sys, default_read, default_write);
	register_sys_interface(console_debug_output, debug_output_sys_read, debug_output_sys_write);
	return 0;
}

//TODO Why are we calling all init consoles here?

int console_init()
{
	if (console_drivers == null) {
		//Initialization of the kernel fails here
		return ENODEV;
	}
	struct console_driver *driver;
	bool functional_drivers = false;
	list_for(console_drivers, driver) {
		int err = driver->init();
		if(!err && (driver->flags & CONSOLE_BUFFERED)) {
			uint_t console_sz = driver->width * driver->height;
			memset(driver->buffer, 0, console_sz);
			err = driver->update_buffer(0, 0, driver->width,
					driver->height);
			if(err)
				continue;
			driver->cursor_x = 0;
			driver->cursor_y = 0;
			err = driver->update_hardware_cursor();
			if(err)
				continue;
			functional_drivers = true;
		}
	}
	if(!functional_drivers)
		return ENODEV;
	return 0;
}
